﻿#pragma once

#include "common.h"
#include "font.h"
#include "bind.h"
#include <set>

#define ANIP_ui_LoopOverChildren(var) \
	if (firstChild() != nullptr)\
	for (Control* var = firstChild(); var != nullptr; var = var->next())

#define ANIP_ui_LoopOverChildrenFrom(first, var) \
	if ((first) != nullptr)\
	for (anip::ui::Control* var = (first); var != nullptr; var = var->next())
		
// ANIP_TODO: unifiy ANIP_ and anip_
#define anip_control_ornament_class static_assert(\
	std::is_base_of_v<Control, C>, \
	"template parameter C must be derived from anip::ui::Control")

namespace anip
{
	class Shape;
	class ShaderPart;
	class Shader;

	namespace ui
	{
		constexpr bool UseDebug = true;

		class Control;
		class Container;

		struct Signal
		{
			enum class Type
			{
				Unknown,
				NeedsRedraw,
				NeedsReflow,
				Animation,
				UpdateScrollableArea,
				ModalPhaseStart, // ANIP_TODO: implement
				ModalPhaseEnd // ANIP_TODO: implement
			};

			Type type;
			void
				* parameter = nullptr;

			bool operator<(const Signal& a) const
			{
				return type == a.type 
					? parameter < a.parameter 
					: (int)type < (int)a.type;
			}
		};

		class Flow
		{
		public:
			vec2 
				dockableStart, 
				dockableEnd;

			float 
				containerWidth,
				containerHeight,
				columnFactor,
				borderLeft,
				borderRight,

				rowWidth = 0,
				lineHeight = 0,
				currentX,
				currentY;

			bool
				hasRowHeader = false;

			Flow(const Container& c);
			Flow(invec2 size, invec2 marginSum, float fac);

			void newLine();
		};

		class Control
		{
		public:
			struct LayoutOptions
			{
				enum { Manual, Flow }
				type = Manual;

				enum class DockType
				{ None, Left, Top, Right, Bottom, Fill }
				dock = DockType::None;

				// 0 = do not stretch; 1 = occupy all of the remaining X/Y space; 
				// 0.5 = occupy a half of it, etc. Note: stretchY is currently not in use
				float 
					stretchX = 0,
					stretchY = 0;

				bool
					allowScrollerH = false,
					allowScrollerV = true;

				// Disable auto-sizing according to the content (mostly for text controls)
				bool noAutoSize = false;

				// Minimal size restriction for the control
				vec2 minSize = { 0, 0 };
			};
			
		private:
			static Control
				* _TopLevel,
				* _DraggingControl;

			static float _UIScaling;

			bool 
				_dragPossible = false,
				_dragging = false, 
				_delete = false;
			vec2 _dragStart;
			std::set<Signal> _signals;

			void _postSignal(const Signal& s);
			void _terminateSignal(const Signal& s);

		protected:
			//// HIDDEN PROPERTIES (I'm still transforming getter/setters to bindables)
			bvec2 scroll = vec2{ 0, 0 };
			// ANIP_TODO: pos, size

			//// INTERNAL STATE VARIABLES
			bool
				_press = false,
				_hover = false,
				_dragOver = false,
				_focus = false;
			MouseButton _pressBtn = MouseButton::None;

			vec2 
				_size{}, _pos{}, _absPos{}, 
				_margin{}, _marginDown{}, 
				_maxScroll{};

			float _flowColFac = 0.3f;

			Control 
				* _parent = nullptr, 
				* _prev = nullptr, 
				* _next = nullptr, 
				* _firstChild = nullptr, 
				* _lastChild = nullptr,
				* _focusedChild = nullptr, 
				* _hoveredChild = nullptr,
				* _dragOverChild = nullptr;

			Font 
				_font{},
				_inheritFont{};
			wstring 
				_text;

			//// HELPER FUNCTIONS

			void markTopLevel()
			{
				assert(_TopLevel == nullptr);
				_TopLevel = this;
			}

			template<typename T>
			void registerBindableRedraw(bindable<T>& p)
			{
				p.registerListener(this,
					[&](bindable<T>&) { postSignal({ Signal::Type::NeedsRedraw }); });
			}

			template<typename T>
			void registerBindableReflow(bindable<T>& p)
			{
				p.registerListener(this,
					[&](bindable<T>&) { postSignal({ Signal::Type::NeedsReflow }); });
			}

			//// HIDDEN FUNCTIONS

			virtual void addChild(Control& child, bool top = false);
			virtual void insertChild(Control& child, Control& before);
			virtual void detachChild(Control& c);
			virtual void replaceChild(Control& child, Control& repl);

			Control* firstChild() const { return _firstChild; }
			Control* lastChild() const { return _lastChild; }
			Control* hoveredChild() const { return _hoveredChild; }
			Control* focusedChild() const { return _focusedChild; }
			Control* draggedOverChild() const { return _dragOverChild; }

			void forceDehoverChildren();
			void forceDefocusChildren();

			bool isDragging() const { return _dragging; }

			void setMargins(invec2 m, invec2 m2)
			{
				_margin = m;
				_marginDown = m2;
				postSignal({ Signal::Type::NeedsReflow });
			}

			void paintChildren();

			// auxillary functions
			void updateMaxScroll();
			void defaultFlowLayout(Flow& f);
			void flowChildrenDefault();
			virtual void flowChildren(Flow& f);

			void retrieveEvents();
			void mainLoop();
			
			//// CALLBACK FUNCTIONS

			virtual bool isPointInside(invec2 m);

			virtual void processMouseMove(invec2 pos);
			virtual void processMouseButton(MouseButton btn, ButtonAction action);
			virtual void processMouseEnter();
			virtual void processMouseLeave();
			virtual void processFocus();
			virtual void processDefocus();
			virtual void processParentResize();
			virtual void processScroll(invec2 pos);
			virtual void processKey(Key k, ButtonAction action);
			virtual void processUnicodeInput(wchar_t ch);
			virtual void processTopLevelFrame() {} // only used for the top level control
			virtual void processMarkedDelete();

			virtual bool canBeginDrag(MouseButton btn, invec2 pos) { return false; }
			virtual void processDrag(invec2 start, invec2 pos) {}
			virtual void processEndDrag() {}

		public:
			static Container* topContainer() { return (Container*)_TopLevel; }
			static Control* currentDragging() { return _DraggingControl; }
			static float UIScaling() { return _UIScaling; }
			static void setUIScaling(float s);

		public:
			/// <summary>
			/// Pointer to arbitrary user-defined data. It is never used by the 
			/// `Control` itself.
			/// </summary>
			void* userData = nullptr;
			
			std::function<void(Control& self)>
				onParentResize,
				onMouseEnter,
				onMouseLeave,
				onFocus;

			/// <summary>
			/// User handler for defocusing. Will be called whenever the value of
			/// focused() changes, INCLUDING when the main window is minimized.
			/// </summary>
			std::function<void(Control& self)> onDefocus;

			/// <summary>
			/// User handler for painting control. Will be called AFTER the native
			/// painting operations and BEFORE the painting of the children controls
			/// (if any).
			/// </summary>
			std::function<void(Control& self)> onPaint;

			/// <summary>
			/// User handler for deleting. Will be called when the control is marked
			/// for deletion (note: NOT immediately before the actual deletion).
			/// </summary>
			std::function<void(Control& self)> onDeleting;

			/// <summary>
			/// User handler for mouse movement. Will be called when mouse movement
			/// is detected while the mouse is inside the control (INCLUDING its
			/// children controls), i.e. when hover() == true.
			/// </summary>
			std::function<void(Control& self, invec2 pos)> onMouseMove;

			/// <summary>
			/// User handler for mouse button action. Will be called when a mouse
			/// button action is detected while the mouse is inside the control 
			/// (INCLUDING its children controls), i.e. when hover() == true.
			/// </summary>
			std::function<void(Control& self, MouseButton btn, ButtonAction action)>
				onMouseButton;

			/// <summary>
			/// User handler for key events. Will be called when a key action
			/// is detected while the control is focused [focused() == true],
			/// i.e. also when any of its children is focused.
			/// </summary>
			std::function<void(Control& self, Key k, ButtonAction action)>
				onKeyAction;

			LayoutOptions layoutOptions{};
			
			/// <summary>
			/// If set to `true`, this Control will ignore the scrolling of the
			/// parent in determining its physical position.
			/// </summary>
			bool fixedPosition = false;

			Control(Container* parent = nullptr, invec2 pos = { 0, 0 },
				invec2 size = { 0, 0 }, const wstring& text = L"");
			Control(Control&) = delete;
			
			virtual ~Control();

			float flowColumnFactor() const { return _flowColFac; }
			void setFlowColumnFactor(float x)
			{
				_flowColFac = x;
				flowChildrenDefault();
			}

			vec2 position() const { return _pos; }
			vec2 size() const { return _size; }

			vec2 toAbsolutePos(invec2 local = { 0, 0 }) const
			{
				return local + _absPos + _margin - scroll();
			}
			vec2 toLocalPos(invec2 absolute) const
			{
				return absolute - _absPos - _margin + scroll();
			}

			virtual void setPosition(invec2 pos);
			virtual void setSize(invec2 size);

			vec2 margin() const { return _margin; }
			vec2 marginBottom() const { return _marginDown; }

			void bringToFront();
			void bringToBack();

			Control* next() const { return _next; }
			Control* previous() const { return _prev; }
			Control* parent() const { return _parent; }

			void setInheritFont(const Font& f);
			Font& getInheritFont() { return _inheritFont; }
			
			virtual void setFont(const Font& f) { _font = f; }
			virtual Font& getFont() { return _font; }

			virtual void setText(const wstring& t) { _text = t; }
			virtual wstring getText() const { return _text; }

			bool pressed() const { return _press; }
			bool hovered() const { return _hover; }
			bool focused() const { return _focus; }
			bool dragOver() const { return _dragOver; }

			void focus();

			vec2 maxScroll() const { return _maxScroll; }

			const std::set<Signal> signals() { return _signals; }
			bool hasSignal(const Signal& s) { return _signals.find(s) != _signals.end(); }

			void markDelete()
			{
				assert(_parent != nullptr);
				_delete = true;
				_parent->postSignal({ Signal::Type::NeedsReflow });
				processMarkedDelete();
			}

			bool markedDelete() const { return _delete; }

			void postSignal(const Signal& s);
			void terminateSignal(const Signal& s);

			virtual void paint()
			{
				if (onPaint) onPaint(*this);
			}

			virtual void flow(Flow& f);
		};

		class Container : public Control
		{
		public:
			using Control::Control;

			using Control::addChild;
			using Control::insertChild;
			using Control::detachChild;
			using Control::replaceChild;

			using Control::firstChild;
			using Control::lastChild;
			using Control::hoveredChild;
			using Control::focusedChild;
			using Control::draggedOverChild;

			using Control::margin;
			using Control::marginBottom;
			using Control::setMargins;
			using Control::flowChildrenDefault;
			//using Control::flowChildren;
		};
	}
}